﻿using System;
using UtilTools;
using System.Net;
using System.Threading;
using System.Net.Sockets;

namespace EleBaService
{
    /// <summary>
    /// Socket Client
    /// </summary>
    public class SktClient
    {
        /// <summary>
        /// 
        /// </summary>
        public Socket Skt;
        /// <summary>
        /// 关闭事件
        /// </summary>
        public event Action OnClose;
        /// <summary>
        /// 
        /// </summary>
        readonly Byte[] ReceiveBuffer;
        /// <summary>
        /// 接收事件
        /// </summary>
        public event Action<Socket, Byte[]> OnReceive;
        /// <summary>
        /// 连接事件
        /// </summary>
        public event Action<Boolean, String, Int32> OnConnect;
        /// <summary>
        /// 接收事件
        /// </summary>
        SocketAsyncEventArgs ReceiveAsyncEventArgs;
        /// <summary>
        /// IP
        /// </summary>
        public String Ip { get; set; }
        /// <summary>
        /// 端口
        /// </summary>
        public Int32 Port { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public SktClient()
        {
            ReceiveBuffer = new Byte[1024];
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="BufferSize"></param>
        public SktClient(Int32 BufferSize)
        {
            ReceiveBuffer = new Byte[BufferSize];
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public void Connect(String ip, Int32 port)
        {
            this.Ip = ip; this.Port = port;
            IPEndPoint LocalEIPoint = LocalIPoint(ip, port);
            Skt = new Socket(LocalEIPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            SocketAsyncEventArgs SocketAsyncEvent = new SocketAsyncEventArgs();
            SocketAsyncEvent.Completed += CompletedEvent;
            SocketAsyncEvent.RemoteEndPoint = LocalEIPoint;
            if (!Skt.ConnectAsync(SocketAsyncEvent)) { ConnectCallBack(SocketAsyncEvent); }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="buffer"></param>
        public void Send(Byte[] buffer)
        {
            try
            {
                if (Skt?.Connected == true) { Skt.Send(buffer); }
                else { Skt?.Close(); Connect(Ip, Port); }
            }
            catch { OnConnect?.Invoke(false, Ip, Port); }
        }
        /// <summary>
        /// 
        /// </summary>
        public void Close() => CloseSocket(ReceiveAsyncEventArgs);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        void ConnectCallBack(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                ReceiveAsyncEventArgs = new SocketAsyncEventArgs();
                ReceiveAsyncEventArgs.SetBuffer(ReceiveBuffer, 0, ReceiveBuffer.Length);
                ReceiveAsyncEventArgs.Completed += CompletedEvent;
                if (!Skt.ReceiveAsync(ReceiveAsyncEventArgs))
                {
                    ReceiveCallBack(ReceiveAsyncEventArgs);
                }
                OnConnect?.Invoke(true, Ip, Port);
            }
            else { OnConnect?.Invoke(false, Ip, Port); }
        }
        void SendCallBack(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success) { Console.WriteLine("发送完成"); }
        }
        void ReceiveCallBack(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                Byte[] data = new Byte[e.BytesTransferred];
                Buffer.BlockCopy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);
                OnReceive?.Invoke(Skt, data);
                if (Skt.Connected == true)
                {
                    if (!Skt.ReceiveAsync(e)) { ReceiveCallBack(e); }
                }
            }
        }
        void CompletedEvent(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ReceiveCallBack(e);
                    break;
                case SocketAsyncOperation.Send:
                    SendCallBack(e);
                    break;
                case SocketAsyncOperation.Connect:
                    ConnectCallBack(e);
                    break;
                default:
                    throw new ArgumentException("套接字上完成的最后一个操作不是接收或发送或连接。");
            }
        }
        void CloseSocket(SocketAsyncEventArgs e)
        {
            if (!Skt.Connected) { return; }
            if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                try { Skt.Shutdown(SocketShutdown.Both); }
                catch (Exception) { }
                Skt.Close();
                OnClose?.Invoke();
            }
        }
        IPEndPoint LocalIPoint(String ip, Int32 port)
        {
            if (!IPAddress.TryParse(ip, out IPAddress ipaddr))
            {
                IPAddress[] iplist = Dns.GetHostAddresses(ip);
                if (iplist != null && iplist.Length > 0) { ipaddr = iplist[0]; }
            }
            return new IPEndPoint(ipaddr, port);
        }

    }
}
